package com.github.terralian.fastmaj.tehai;

import java.util.Collection;

import com.github.terralian.fastmaj.encode.Encode34;
import com.github.terralian.fastmaj.game.IGameCore;
import com.github.terralian.fastmaj.hai.IHai;

/**
 * 向听数计算器接口
 * <p/>
 * 该接口提供对手牌{@link ITehai}的向听计算，及对{@link Encode34}编码格式数组的向听数计算。
 * 当为{@link ITehai}时，支持鸣牌暗杠。
 * <p/>
 * <note><b>这里的向听数定义为：向听数 = 到和牌时，最少需要换的手牌数 + 1</b>.
 * <p/>
 * 如鸣牌后手牌为1356m， 则更换1枚手牌为1156m或1456m即听牌，所以向听数为1。 类似的若手牌为11122233334444z，
 * 看似已经听牌，但是到和牌最少需要换2枚手牌，所以向听数为2。
 * <p/>
 * 该接口的实现类支持手牌为 （对子1 + 面子 * n） 或者 （对子1 + 面子 * n -1）形式的计算。
 * 即若鸣牌后手牌剩余1m未摸牌，或摸牌后手牌为12m时的向听计算。顺带一提，对于上述两者的向听都是1。
 * <p/>
 * 基于定义，向听数的用处很多，其中如为程序提供玩家是否听牌或者和了的标准，向听数可以很容易的判断是否为玩家展示荣和或自摸按钮。
 * 但是向听数计算起来也十分费时，该接口的实现类提供了不同种方案的计算方式，如基于回溯法或哈希法，各有各的优点。
 * <p/>
 * 由于向听数实在于基石，该项目的游戏核心类{@link IGameCore}集成了向听数的计算，可以直接获取。<b>当使用游戏核心进行游戏对局时，
 * 切记无需二次使用向听数计算类，可以省掉一大笔不必要的计算开销。</b>
 * 
 * @author terra.lian
 */
public interface ISyatenCalculator {

    /**
     * 计算最小向听数
     * <p/>
     * 该方法是该接口三个方法的组合，在未副露的情况下，最小向听会取七对，国士，和标准形式的其中最小值返回。在副露时（如暗杠，鸣牌），
     * 由于七对子和国士不再成立，此时仅计算标准形式{@link #calcNormal}的向听数。
     * <p/>
     * 一般情况下该接口的使用率最高，仅特殊需求下才需要分别调用其他方法进行分别计算。
     * 
     * @param hands 手牌
     */
    int calcMin(Collection<IHai> hands);

    /**
     * 计算最小向听数
     * <p/>
     * 该方法是该接口三个方法的组合，在未副露的情况下，最小向听会取七对，国士，和标准形式的其中最小值返回。在副露时（如暗杠，鸣牌），
     * 由于七对子和国士不再成立，此时仅计算标准形式{@link #calcNormal}的向听数。
     * <p/>
     * 一般情况下该接口的使用率最高，仅特殊需求下才需要分别调用其他方法进行分别计算。
     * 
     * @param value34 手牌的向听数
     */
    int calcMin(int[] value34);

    /**
     * 计算七对子形式的向听数
     * <p/>
     * 该方法意义上而言，仅在在手牌数为13或者14枚时成立，即若手牌鸣牌时七对子役不再成立，向听数也就没有意义。
     * 但是为了性能，一般情况该方法不会校验传入的手牌枚数是否合法。
     * <p/>
     * 七对子的计算公式为：向听数= 6 - 对子数。并且在牌种类数不足7时，向听数 = 向听数 + 7 - 牌的种类。
     * 
     * @param hands 手牌
     */
    default int calcTiitoitu(Collection<IHai> hands) {
        return calcTiitoitu(Encode34.toEncode34(hands));
    }

    /**
     * 计算七对子形式的向听数
     * <p/>
     * 该方法意义上而言，仅在在手牌数为13或者14枚时成立，即若手牌鸣牌时七对子役不再成立，向听数也就没有意义。
     * 但是为了性能，一般情况该方法不会校验传入的手牌枚数是否合法。
     * <p/>
     * 七对子的计算公式为：向听数= 6 - 对子数。并且在牌种类数不足7时，向听数 = 向听数 + 7 - 牌的种类。
     *
     * @param value34 手牌（34编码）
     */
    int calcTiitoitu(int[] value34);

    /**
     * 计算国士向听
     * <p/>
     * 方法意义上而言，仅在在手牌数为13或者14枚时成立，即若手牌鸣牌时国士役不再成立，向听数也就没有意义。
     * 但是为了性能，一般情况该方法不会校验传入的手牌枚数是否合法。
     * <p/>
     * 国士的向听计算公式为：向听数 = 13 - 幺九牌种类数。并且在幺九牌存在对子的场合时，向听数 += 1。
     * 
     * @param hands 手牌
     */
    default int calcKokusi(Collection<IHai> hands) {
        return calcKokusi(Encode34.toEncode34(hands));
    }

    /**
     * 计算国士向听
     * <p/>
     * 方法意义上而言，仅在在手牌数为13或者14枚时成立，即若手牌鸣牌时国士役不再成立，向听数也就没有意义。
     * 但是为了性能，一般情况该方法不会校验传入的手牌枚数是否合法。
     * <p/>
     * 国士的向听计算公式为：向听数 = 13 - 幺九牌种类数。并且在幺九牌存在对子的场合时，向听数 += 1。
     * 
     * @param value34 手牌（34编码）
     */
    int calcKokusi(int[] value34);

    /**
     * 计算标准向听数
     * <p/>
     * 形如1个雀头，4个面子的和牌类型，或者可以说除了七对子和国士外的牌型都可以归为标准牌型。 该类型支持鸣牌暗刻的对手牌固定形式，
     * 支持对（对子1 + 面子 * n） 或者 （对子1 + 面子 * n -1）形式的计算。
     * <p/>
     * 由于标准向听数的计算比较复杂且费事，对于不同实现的向听数计算算法，该方法是最需要进行优化的点，并且
     * 不同方式实现的标准向听数计算差异很大，可以根据使用场景选择不同的实现类。
     * <p/>
     * 标准型的向听数计算公式为：向听数 = 8 - 2*面子数 - 对子数 - 搭子数，当手牌存在雀头时，向听数 += 1。
     * 
     * @param hands 手牌
     */
    int calcNormal(Collection<IHai> hands);

    /**
     * 计算标准向听数
     * <p/>
     * 形如1个雀头，4个面子的和牌类型，或者可以说除了七对子和国士外的牌型都可以归为标准牌型。 该类型支持鸣牌暗刻的对手牌固定形式， 支持对（对子1 + 面子
     * * n） 或者 （对子1 + 面子 * n -1）形式的计算。
     * <p/>
     * 由于标准向听数的计算比较复杂且费事，对于不同实现的向听数计算算法，该方法是最需要进行优化的点，并且
     * 不同方式实现的标准向听数计算差异很大，可以根据使用场景选择不同的实现类。
     * <p/>
     * 标准型的向听数计算公式为：向听数 = 8 - 2*面子数 - 对子数 - 搭子数，当手牌存在雀头时，向听数 += 1。
     * 
     * @param value34 手牌（34编码）
     */
    int calcNormal(int[] value34);
}
